/*
 * @(#)FactStorageManager.java 1.0 2000/12/14
 * 
 * Copyright 2000 Thomas Barnekow. All Rights Reserved.
 * 
 * This software is the proprietary information of Thomas Barnekow. Use is
 * subject to license terms.
 */

package jessx.provider;

import java.util.*;
import jess.*;

import tb.provider.*;
import tb.util.*;

import jessx.*;
import jessx.util.*;

/**
 * The Fact Storage Manager.
 * 
 * @author Thomas Barnekow
 * @version 1.0
 */
public class FactStorageManager {

	/**
	 * Prefix of system names.
	 */
	public final static String SYSTEM_PREFIX = "__";

	/**
	 * String prepended to deftemplate names to form backwards chaining goals.
	 * Defined with scope package private within jess.RU.
	 */
	public static final String BACKCHAIN_PREFIX = "need-";

	/**
	 * Special multislot name used for ordered facts. Defined with scope
	 * package private within jess.RU.
	 */
	public static final String DEFAULT_SLOT_NAME = "__data";

	/**
	 * FactStorageManagers by {@link jess.Rete}engine.
	 */
	private static Map m_fsms = new HashMap();

	/**
	 * Our {@link jess.Rete}engine
	 */
	private Rete m_engine;

	/**
	 * Entity names by {@link jess.Fact}names
	 */
	private Map m_entities = new HashMap();

	/**
	 * Don't let'em create instances.
	 */
	private FactStorageManager(Rete engine) {
		m_engine = engine;
	}

	/**
	 * Get the fact storage manager for the given {@link jess.Rete}engine.
	 */
	public static FactStorageManager getFactStorageManager(Rete engine) {
		FactStorageManager fsm = (FactStorageManager) m_fsms.get(engine);
		if (fsm == null) {
			fsm = new FactStorageManager(engine);
			m_fsms.put(engine, fsm);
		}
		return fsm;
	}

	/**
	 * Activate backward chaining for given entity. The default fact name
	 * <code>entityName.toString("-")</code> will be mapped to the qualified
	 * <code>entityName</code>. For example, if the entity's name is
	 * "scott.emp" the the default fact name will be "scott-emp".
	 * 
	 * @param entityName
	 *            name of entity (e.g., database table)
	 * @param engine
	 *            the Rete engine
	 * @see QualifiedName#toString(String)
	 */
	public static void doBackwardChaining(
		QualifiedName entityName,
		Rete engine)
		throws JessException {
		getFactStorageManager(engine).doBackwardChaining(entityName);
	}

	/**
	 * Activate backward chaining for given entity.
	 * 
	 * @param entityName
	 *            name of entity (e.g., database table)
	 * @param factName
	 *            name of backchain reactive fact
	 * @param engine
	 *            the Rete engine
	 */
	public static void doBackwardChaining(
		QualifiedName entityName,
		String factName,
		Rete engine)
		throws JessException {
		getFactStorageManager(engine).doBackwardChaining(entityName, factName);
	}

	/**
	 * Activate backward chaining for given entity. The default fact name
	 * <code>entityName.toString("-")</code> will be mapped to the qualified
	 * <code>entityName</code>. For example, if the entity's name is
	 * "scott.emp" the the default fact name will be "scott-emp".
	 * 
	 * @param entityName
	 *            name of entity (e.g., database table)
	 * @see QualifiedName#toString(String)
	 */
	public void doBackwardChaining(QualifiedName entityName)
		throws JessException {
		doBackwardChaining(entityName, entityName.toString("-"));
	}

	/**
	 * Activate backward chaining for given entity. The given <code>factName</code>
	 * will be mapped to the qualified <code>entityName</code>.
	 * 
	 * @param entityName
	 *            name of entity (e.g., database table)
	 * @param factName
	 *            name of backchain reactive fact
	 */
	public void doBackwardChaining(QualifiedName entityName, String factName)
		throws JessException {
		// Load Userpackages and advise (assert) and (retract)
		initFunctions();

		// We will only support unordered facts
		Deftemplate dt = m_engine.findDeftemplate(factName);
		if (dt == null)
			throw new JessException(
				"BackwardChainingMediatorImpl.doBackwardChaining()",
				"Error: deftemplate not found",
				factName);

		if (dt.getSlotIndex(DEFAULT_SLOT_NAME) >= 0)
			throw new JessException(
				"BackwardChainingMediatorImpl.doBackwardChaining()",
				"Error: backward chaining is not supported for ordered facts",
				factName);

		// Activate backward chaining
		m_engine.executeCommand("(do-backward-chaining " + factName + ")");

		// Define rule waiting for the goal seeker
		StringBuffer defrule = new StringBuffer(256);
		defrule.append("(defrule __backchain-").append(factName).append('\n');
		defrule.append("  ?goal <- (").append(BACKCHAIN_PREFIX).append(
			factName);

		for (int i = 0; i < dt.getNSlots(); i++) {
			String slotName = dt.getSlotName(i);
			if (!slotName.startsWith(SYSTEM_PREFIX))
				defrule.append(" (").append(slotName).append(" ?v").append(
					i).append(
					')');
		}
		defrule.append(')').append('\n');

		defrule.append("  =>").append('\n');

		defrule
			.append("  (bind $?facts (")
			.append(ProviderFunctions.SELECT)
			.append(" ?goal))")
			.append('\n');
		defrule
			.append("  (")
			.append(AdviceFunctions.EXPAND)
			.append(" (assert $?facts))")
			.append('\n');
		defrule.append("  (retract ?goal))");

		// Actually define the rule in our Rete engine
		m_engine.executeCommand(defrule.toString());

		// Register entity
		m_entities.put(factName, entityName);
	}

	/**
	 * Load required functions.
	 */
	public static void initFunctions(Rete engine) throws JessException {
		getFactStorageManager(engine).initFunctions();
	}

	/**
	 * Load required functions.
	 */
	public void initFunctions() throws JessException {
		if (m_engine.findUserfunction(ProviderFunctions.SELECT) == null)
			m_engine.addUserpackage(new ProviderFunctions());
	}

	/**
	 * Get the entity name corresponding to the given fact name.
	 */
	public static QualifiedName getEntityName(String factName, Rete engine) {
		return getFactStorageManager(engine).getEntityName(factName);
	}

	/**
	 * Get the entity name corresponding to the given fact name.
	 */
	public QualifiedName getEntityName(String factName) {
		return (QualifiedName) m_entities.get(factName);
	}

	/**
	 * Find a registered {@link FactReader}by entity name.
	 */
	public static FactReader findFactReader(QualifiedName entityName)
		throws JessException {
		try {
			return (FactReader) ProviderRegistry.findProvider(
				FactReader.class,
				StateProvider.FULL_SELECT,
				entityName);
		} catch (Exception ex) {
			throw new JessException(
				"FactStorageManager.findFactReader(QualifiedName)",
				"No FactReader for entity:",
				entityName.toString());
		}
	}

	/**
	 * Find a registered {@link FactWriter}by entity name.
	 */
	public static FactWriter findFactWriter(QualifiedName entityName)
		throws JessException {
		if (entityName == null)
			throw new JessException(
				"FactStorageManager.findFactWriter(QualifiedName)",
				"entityName = null",
				"");
		try {
			return (FactWriter) ProviderRegistry.findProvider(
				FactWriter.class,
				StateProvider.FULL_WRITE,
				entityName);
		} catch (Exception ex) {
			throw new JessException(
				"FactStorageManager.findFactWriter(QualifiedName)",
				"No FactWriter for entity:",
				entityName.toString());
		}
	}

	/**
	 * Find a registered {@link MetaDataProvider}by entity name.
	 */
	public static MetaDataProvider findMetaDataProvider(
		QualifiedName entityName,
		QualifiedName service)
		throws JessException {
		try {
			return (MetaDataProvider) ProviderRegistry.findProvider(
				MetaDataProvider.class,
				service,
				entityName);
		} catch (Exception ex) {
			throw new JessException(
				"FactStorageManager.findMetaDataProvider(QualifiedName, QualifiedName)",
				"No MetaDataprovider for service/entity:",
				service + "/" + entityName);
		}
	}

	/**
	 * Read fact(s) from persistent storage.
	 */
	public static ValueVector select(Fact goal, Context context)
		throws JessException {
		String goalName = JessxTools.getFactName(goal);
		String factName =
			goalName.startsWith(BACKCHAIN_PREFIX)
				? goalName.substring(BACKCHAIN_PREFIX.length())
				: goalName;

		QualifiedName entityName = getEntityName(factName, context.getEngine());
		FactReader connector = findFactReader(entityName);
		return connector.select(entityName, goal, context);
	}

	/**
	 * Writes fact to persistent storage.
	 */
	public static Fact assertFact(Fact fact, Context context)
		throws JessException {
		QualifiedName entityName =
			getEntityName(JessxTools.getFactName(fact), context.getEngine());
		FactWriter connector = findFactWriter(entityName);
		return connector.assertFact(entityName, fact, context);
	}

	/**
	 * Deletes persistent fact.
	 */
	public static Fact retract(Fact fact, Context context)
		throws JessException {
		QualifiedName entityName =
			getEntityName(JessxTools.getFactName(fact), context.getEngine());
		FactWriter connector = findFactWriter(entityName);
		return connector.retract(entityName, fact, context);
	}

	public static MetaObject describe(QualifiedName entityName)
		throws JessException {
		return describe(entityName, MetaDataProvider.DECLARATION);
	}

	/**
	 * Describe entity
	 */
	public static MetaObject describe(
		QualifiedName entityName,
		QualifiedName service)
		throws JessException {
		MetaDataProvider mdp = findMetaDataProvider(entityName, service);
		try {
			return mdp.describe(entityName);
		} catch (Exception ex) {
			throw new JessException(
				"FactStorageManager.describe(QualifiedName, QualifiedName)",
				"MetaDataProvider threw an exception",
				ex);
		}
	}
}